/*
 * Copyright 2000-2016 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.openapi.util.text;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Stripped-down version of {@code com.intellij.openapi.util.text.StringUtil}.
 * Intended to use by external (out-of-IDE-process) runners and helpers so it should not contain any library dependencies.
 *
 * @since 12.0
 */
@SuppressWarnings("UtilityClassWithoutPrivateConstructor")
public class StringUtilRt {
    @Contract(pure = true)
    public static boolean charsEqualIgnoreCase(char a, char b) {
        return a == b || toUpperCase(a) == toUpperCase(b) || toLowerCase(a) == toLowerCase(b);
    }

    @NotNull
    @Contract(pure = true)
    public static CharSequence toUpperCase(@NotNull CharSequence s) {
        StringBuilder answer = null;

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            char upCased = toUpperCase(c);
            if (answer == null && upCased != c) {
                answer = new StringBuilder(s.length());
                answer.append(s.subSequence(0, i));
            }

            if (answer != null) {
                answer.append(upCased);
            }
        }

        return answer == null ? s : answer;
    }

    @Contract(pure = true)
    public static char toUpperCase(char a) {
        if (a < 'a') {
            return a;
        }
        if (a <= 'z') {
            return (char) (a + ('A' - 'a'));
        }
        return Character.toUpperCase(a);
    }

    @Contract(pure = true)
    public static char toLowerCase(char a) {
        if (a < 'A' || a >= 'a' && a <= 'z') {
            return a;
        }

        if (a <= 'Z') {
            return (char) (a + ('a' - 'A'));
        }

        return Character.toLowerCase(a);
    }

    /**
     * Converts line separators to {@code "\n"}
     */
    @NotNull
    @Contract(pure = true)
    public static String convertLineSeparators(@NotNull String text) {
        return convertLineSeparators(text, false);
    }

    @NotNull
    @Contract(pure = true)
    public static String convertLineSeparators(@NotNull String text, boolean keepCarriageReturn) {
        return convertLineSeparators(text, "\n", null, keepCarriageReturn);
    }

    @NotNull
    @Contract(pure = true)
    public static String convertLineSeparators(@NotNull String text, @NotNull String newSeparator) {
        return convertLineSeparators(text, newSeparator, null);
    }

    @NotNull
    @Contract(pure = true)
    public static CharSequence convertLineSeparators(@NotNull CharSequence text, @NotNull String newSeparator) {
        return unifyLineSeparators(text, newSeparator, null, false);
    }

    @NotNull
    public static String convertLineSeparators(@NotNull String text, @NotNull String newSeparator, @Nullable int[] offsetsToKeep) {
        return convertLineSeparators(text, newSeparator, offsetsToKeep, false);
    }

    @NotNull
    public static String convertLineSeparators(@NotNull String text,
                                               @NotNull String newSeparator,
                                               @Nullable int[] offsetsToKeep,
                                               boolean keepCarriageReturn) {
        return unifyLineSeparators(text, newSeparator, offsetsToKeep, keepCarriageReturn).toString();
    }

    @NotNull
    private static CharSequence unifyLineSeparators(@NotNull CharSequence text,
                                                    @NotNull String newSeparator,
                                                    @Nullable int[] offsetsToKeep,
                                                    boolean keepCarriageReturn) {
        StringBuilder buffer = null;
        int intactLength = 0;
        final boolean newSeparatorIsSlashN = "\n".equals(newSeparator);
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c == '\n') {
                if (!newSeparatorIsSlashN) {
                    if (buffer == null) {
                        buffer = new StringBuilder(text.length());
                        buffer.append(text, 0, intactLength);
                    }
                    buffer.append(newSeparator);
                    shiftOffsets(offsetsToKeep, buffer.length(), 1, newSeparator.length());
                } else if (buffer == null) {
                    intactLength++;
                } else {
                    buffer.append(c);
                }
            } else if (c == '\r') {
                boolean followedByLineFeed = i < text.length() - 1 && text.charAt(i + 1) == '\n';
                if (!followedByLineFeed && keepCarriageReturn) {
                    if (buffer == null) {
                        intactLength++;
                    } else {
                        buffer.append(c);
                    }
                    continue;
                }
                if (buffer == null) {
                    buffer = new StringBuilder(text.length());
                    buffer.append(text, 0, intactLength);
                }
                buffer.append(newSeparator);
                if (followedByLineFeed) {
                    //noinspection AssignmentToForLoopParameter
                    i++;
                    shiftOffsets(offsetsToKeep, buffer.length(), 2, newSeparator.length());
                } else {
                    shiftOffsets(offsetsToKeep, buffer.length(), 1, newSeparator.length());
                }
            } else {
                if (buffer == null) {
                    intactLength++;
                } else {
                    buffer.append(c);
                }
            }
        }
        return buffer == null ? text : buffer;
    }

    private static void shiftOffsets(int[] offsets, int changeOffset, int oldLength, int newLength) {
        if (offsets == null) return;
        int shift = newLength - oldLength;
        if (shift == 0) return;
        for (int i = 0; i < offsets.length; i++) {
            int offset = offsets[i];
            if (offset >= changeOffset + oldLength) {
                offsets[i] += shift;
            }
        }
    }

    @Contract(pure = true)
    public static int parseInt(@Nullable String string, final int defaultValue) {
        if (string == null) {
            return defaultValue;
        }

        try {
            return Integer.parseInt(string);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    @Contract(pure = true)
    public static long parseLong(@Nullable String string, long defaultValue) {
        if (string == null) {
            return defaultValue;
        }
        try {
            return Long.parseLong(string);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    @Contract(pure = true)
    public static double parseDouble(final String string, final double defaultValue) {
        try {
            return Double.parseDouble(string);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    @Contract(pure = true)
    public static boolean parseBoolean(final String string, final boolean defaultValue) {
        try {
            return Boolean.parseBoolean(string);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    @Contract(pure = true)
    static <E extends Enum<E>> E parseEnum(@NotNull String string, E defaultValue, @NotNull Class<E> clazz) {
        try {
            return Enum.valueOf(clazz, string);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    @NotNull
    @Contract(pure = true)
    public static String getShortName(@NotNull Class aClass) {
        return getShortName(aClass.getName());
    }

    @NotNull
    @Contract(pure = true)
    public static String getShortName(@NotNull String fqName) {
        return getShortName(fqName, '.');
    }

    @NotNull
    @Contract(pure = true)
    public static String getShortName(@NotNull String fqName, char separator) {
        int lastPointIdx = fqName.lastIndexOf(separator);
        if (lastPointIdx >= 0) {
            return fqName.substring(lastPointIdx + 1);
        }
        return fqName;
    }

    @Contract(pure = true)
    public static boolean endsWithChar(@Nullable CharSequence s, char suffix) {
        return s != null && s.length() != 0 && s.charAt(s.length() - 1) == suffix;
    }

    @Contract(pure = true)
    public static boolean startsWithIgnoreCase(@NonNls @NotNull String str, @NonNls @NotNull String prefix) {
        final int stringLength = str.length();
        final int prefixLength = prefix.length();
        return stringLength >= prefixLength && str.regionMatches(true, 0, prefix, 0, prefixLength);
    }

    @Contract(pure = true)
    public static boolean endsWithIgnoreCase(@NonNls @NotNull CharSequence text, @NonNls @NotNull CharSequence suffix) {
        int l1 = text.length();
        int l2 = suffix.length();
        if (l1 < l2) return false;

        for (int i = l1 - 1; i >= l1 - l2; i--) {
            if (!charsEqualIgnoreCase(text.charAt(i), suffix.charAt(i + l2 - l1))) {
                return false;
            }
        }

        return true;
    }

    /**
     * Allows to retrieve index of last occurrence of the given symbols at {@code [start; end)} sub-sequence of the given text.
     *
     * @param s     target text
     * @param c     target symbol which last occurrence we want to check
     * @param start start offset of the target text (inclusive)
     * @param end   end offset of the target text (exclusive)
     * @return index of the last occurrence of the given symbol at the target sub-sequence of the given text if any;
     * {@code -1} otherwise
     */
    @Contract(pure = true)
    public static int lastIndexOf(@NotNull CharSequence s, char c, int start, int end) {
        start = Math.max(start, 0);
        for (int i = Math.min(end, s.length()) - 1; i >= start; i--) {
            if (s.charAt(i) == c) return i;
        }
        return -1;
    }
}